package protocol

import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"hash/crc32"
	"strconv"
)

// Protocol 通用错误类型
var (
	ErrInvalidMessageFormat = errors.New("invalid message format")
	ErrMessageCRCCheckFail  = errors.New("message CRC check fail")
	ErrInvalidMessageLength = errors.New("invalid message length")
)

// Protocol 报文头、报文尾定义
var (
	MessageHead = []byte{0xFF, 0xFF}
	MessageEnd  = []byte{0xFF, 0xFE}
)

// Message 报文主框架定义
type Message struct {
	Head     [2]byte // 报文头`0xFF 0xFF`
	Length   [4]byte // 报文长度，4位数字，高位补0
	Data     []byte  // 报文数据
	CheckSum [4]byte // 报文CRC32校验位
	End      [2]byte // 报文尾`0xFF 0xFE`
}

// Data 报文数据定义
type Data struct {
	Command  [2]byte  // 报文命令（暨类型）
	ClientID [12]byte // 客户端 ID
	BizData  []byte   // 业务数据
}

// Pack 协议打包
func Pack(dataBytes []byte) []byte {
	packLength := len(dataBytes) + 12  // 数据长度+报文头(2byte)+报文尾(2byte)+报文长度(4byte)+校验位(4byte)
	result := make([]byte, packLength) // 初始化报文长度
	// 报文头
	copy(result[:2], MessageHead)
	// 报文长度
	dataLength := []byte(fmt.Sprintf("%04d", len(dataBytes)))
	copy(result[2:6], dataLength)
	// 报文数据
	copy(result[6:], dataBytes)
	// 校验位
	crcCheck, _ := hex.DecodeString(fmt.Sprintf("%X", crc32.ChecksumIEEE(dataBytes)))
	copy(result[packLength-6:packLength-2], crcCheck)
	// 报文尾
	copy(result[packLength-2:], MessageEnd)
	return result
}

// Unpack 协议拆包
func Unpack(messageBytes []byte) ([]byte, error) {
	messageLength := len(messageBytes)
	// 判断报文基本长度是否符合
	if messageLength < 2+4+4+2 { // 2位报文头+4位报文长度+4位校验位+2位报文尾
		return nil, ErrInvalidMessageLength
	}
	// 判断报文头、报文尾是否符合要求
	if bytes.Equal(messageBytes[:2], MessageHead) && bytes.Equal(messageBytes[messageLength-2:], MessageEnd) {
		// 判断是否能获取报文中定义的数据长度
		dataLength, err := strconv.Atoi(string(messageBytes[2:6]))
		if err != nil {
			return nil, ErrInvalidMessageFormat
		}
		// 判断报文中定义的数据长度是否与报文数据实际长度一致
		dataBytes := messageBytes[6 : messageLength-6]
		if dataLength != len(dataBytes) {
			return nil, ErrInvalidMessageLength
		}
		// 判断报文中定义的CRC校验位是否与报文数据实际的CRC校验位一致
		crcCheckMsg := messageBytes[messageLength-6 : messageLength-2]
		crcCheckReal := make([]byte, 4) // 定长4位，不足低位补空（0x00）
		crcCheck, _ := hex.DecodeString(fmt.Sprintf("%X", crc32.ChecksumIEEE(dataBytes)))
		copy(crcCheckReal, crcCheck)
		if !bytes.Equal(crcCheckMsg, crcCheckReal) {
			return nil, ErrMessageCRCCheckFail
		}
		return dataBytes, nil
	}
	return nil, ErrInvalidMessageFormat
}
